import os
import sys
import time
import json
import logging
import smtplib
import colorlog
import pymysql
import zipfile
import subprocess
import threading
from logging import handlers
from email import encoders
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.base import MIMEBase
from enum import Enum


class Singleton(object):
    def __init__(self, cls):
        self._cls = cls
        self._instance = {}

    def __call__(self):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]


@Singleton
class Logging(object):
    def __init__(self):
        pass

    def create(self):
        log_colors_config = {
            'D': 'white',
            'I': 'green',
            'W': 'yellow',
            'E': 'red',
            'C': 'bold_red',
        }
        logger = logging.getLogger('logger_name')
        # 输出到控制台
        console_handler = logging.StreamHandler()
        # 输出到文件并每日切换
        log_path = "log/build.log"
        file_handler = handlers.TimedRotatingFileHandler(
            filename=log_path, when='D', backupCount=10, encoding='utf-8')
        # 日志级别，logger 和 handler以最高级别为准，不同handler之间可以不一样，不相互影响
        logging.addLevelName(logging.DEBUG, 'D')
        logging.addLevelName(logging.INFO, 'I')
        logging.addLevelName(logging.ERROR, 'E')
        logging.addLevelName(logging.WARNING, 'W')
        logger.setLevel(logging.DEBUG)
        console_handler.setLevel(logging.DEBUG)
        file_handler.setLevel(logging.INFO)
        # 日志输出格式
        file_formatter = logging.Formatter(
            fmt='[%(asctime)s.%(msecs)03d][%(levelname)s] : %(message)s',
            datefmt='%Y-%m-%d  %H:%M:%S'
        )
        console_formatter = colorlog.ColoredFormatter(
            fmt='%(log_color)s[%(asctime)s.%(msecs)03d][%(levelname)s] : %(message)s',
            datefmt='%Y-%m-%d  %H:%M:%S',
            log_colors=log_colors_config
        )
        console_handler.setFormatter(console_formatter)
        file_handler.setFormatter(file_formatter)
        # 重复日志问题：
        # 1、防止多次addHandler；
        # 2、loggername 保证每次添加的时候不一样；
        # 3、显示完log之后调用removeHandler
        if not logger.handlers:
            logger.addHandler(console_handler)
            logger.addHandler(file_handler)

        console_handler.close()
        file_handler.close()
        return logger


class Logger():
    @staticmethod
    def info(content):
        logger = Logging().create()
        logger.info(content)

    @staticmethod
    def debug(content):
        logger = Logging().create()
        logger.debug(content)

    @staticmethod
    def warning(content):
        logger = Logging().create()
        logger.warning(content)

    @staticmethod
    def error(content):
        logger = Logging().create()
        logger.error(content)


# 获得当前日期
def now_date_time():
    now = int(round(time.time()*1000))
    date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(now/1000))
    return date


MAIL_HOST = 'smtp.163.com'
MAIL_USER = 'arkui_tdd_email@163.com'
MAIL_PASSWORD = 'QIHXABIXQABJZSPR'


class EmailUtils():
    def __init__(self, mail_host=MAIL_HOST, mail_user=MAIL_USER, mail_pass=MAIL_PASSWORD):
        self.mail_host = mail_host
        self.mail_user = mail_user
        self.mail_pass = mail_pass
        self.smtp_server = None

    def login_smtp_server(self):
        if self.smtp_server is None:
            self.smtp_server = smtplib.SMTP()
            self.smtp_server.connect(self.mail_host, 25)
            self.smtp_server.login(self.mail_user, self.mail_pass)

    def send_mail(self, email, content):
        message = MIMEMultipart()
        message['From'] = self.mail_user
        message['To'] = email
        message['Subject'] = "ArkUI单元测试系统崩溃/失败用例提醒"
        part = MIMEText(content, 'html', 'utf-8')
        message.attach(part)

        try:
            self.smtp_server.sendmail(
                self.mail_user, email, message.as_string())
            Logger.info(f"send mail to {email} successfully")
        except smtplib.SMTPException as e:
            Logger.error(f"send mail to {email} failed")

    def send_email_with_zip(self, email, content, zip_path, cc=None, bcc=None):
        message = MIMEMultipart()
        message['From'] = self.mail_user
        # 处理收件人
        if isinstance(email, list):
            message['To'] = ', '.join(email)
        else:
            message['To'] = email
        # 处理抄送人
        if cc:
            if isinstance(cc, list):
                # 如果有抄送头并且是抄送列表
                message['Cc'] = ', '.join(cc)
            else:
                message['Cc'] = cc
        message['Subject'] = "ArkUI单元测试系统报告和附件"
        # 添加邮件正文
        message.attach(MIMEText(content, 'html', 'utf-8'))
        # 添加 ZIP 文件附件
        try:
            with open(zip_path, 'rb') as attachment:
                mime_base = MIMEBase('application', 'octet-stream')
                mime_base.set_payload(attachment.read())
                encoders.encode_base64(mime_base)  # 对文件内容进行编码
                mime_base.add_header(
                    'Content-Disposition',
                    f'attachment; filename="{zip_path.split("/")[-1]}"'  # 提取文件名
                )
                message.attach(mime_base)
            # 发送邮件时，处理收件人和抄送人列表
            recipients = []
            if isinstance(email, list):
                recipients.extend(email)
            else:
                recipients.append(email)
            if cc:
                if isinstance(cc, list):
                    recipients.extend(cc)
                else:
                    recipients.append(cc)
            if bcc:
                if isinstance(bcc, list):
                    recipients.extend(bcc)
                else:
                    recipients.append(bcc)
            self.smtp_server.sendmail(
                self.mail_user,
                recipients,
                message.as_string()
            )
            Logger.info(f"emial already sends successfully, attachment: {zip_path}")
        except FileNotFoundError:
            Logger.error(f"attachment {zip_path} not find, do not send mail")
        except smtplib.SMTPException as e:
            Logger.error(f"send mail failed: {e}")

    def close(self):
        if self.smtp_server is not None:
            self.smtp_server.quit()


class DataBaseUtils():
    def __init__(self, host, user, passwd, port, database):
        self.host = host
        self.user = user
        self.passwd = passwd
        self.port = port
        self.database = database
        self.cursor = None
        self.db = None
        self.count = 0

    def connect(self):
        if self.cursor is None and self.db is None:
            try:
                self.db = pymysql.connect(host=self.host, user=self.user, passwd=self.passwd,
                                          port=self.port, database=self.database)
                # 成功连接就把count置为0
                self.count = 0
                Logger.debug(f"connect database {self.database} successfully")
            except:
                # 连接不上就反复重连
                self.count = self.count + 1
                Logger.error(f"reconnect, {self.count} time ")
                if self.count == 10:
                    # 连接10次连不上就停止程序
                    # 暂时先这么处理，合理的方式是数据库连不上之后应该发邮件告知
                    Logger.error(f"connect database {self.database} failed")
                    sys.exit()
                self.connect()
            # 创建一个游标对象
            self.cursor = self.db.cursor()

    def close(self):
        if self.db is not None:
            self.db.close()
            Logger.debug("The database closed successfully")
        else:
            Logger.error("The database has been closed")

    def execute(self, sql):
        # 执行sql
        if self.cursor is not None and self.db is not None:
            try:
                self.cursor.execute(sql)
                self.db.commit()
                Logger.debug("sql execute successfully")
            except:
                self.db.rollback()
                Logger.error(f"SQL: {sql} execution failed, rolled back")
        else:
            Logger.error("database not connected")

    def execute_result(self, sql):
        # 执行sql并返回执行结果
        if self.cursor is not None and self.db is not None:
            try:
                self.cursor.execute(sql)
                self.db.commit()
                results = self.cursor.fetchall()
                return results
            except:
                self.db.rollback()
                Logger.error(f"SQL: {sql} execution failed, rolled back")
                return []
        else:
            Logger.error("database not connected")


class DeviceMode(Enum):
    MASKROM = "maskroom"
    LOADER = "loader"
    NONE = "none"


class Device:
    def __init__(self, ld: str, sn: str, mode: DeviceMode):
        self.ld = ld
        self.sn = sn
        self.mode = mode
        self.state = True


class DeviceManager:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        with cls._lock:
            if not cls._instance:
                cls._instance = super().__new__(cls)
                cls._instance._init_device_list()
            return cls._instance

    def _init_device_list(self):
        try:
            self.flash_tool = '%s/bin/flash.%s' % (self.toolDir, os.uname().machine)
            result = subprocess.run(
                [flash_tool, "LD"],
                capture_output=True,
                text=True,
                check=True
            )
            output = result.stdout.strip()
            raw_device_list = output.split('\n') if output else []
            self.device_list = []
            for raw_device in raw_device_list:
                sn = raw_device
                ld = None
                device = Device(sn, ld)
                self.device_list.append(device)
        except subprocess.CalledProcessError as e:
            Logger.info(f"subprocess run cmd error: {e.stderr}")
            self.device_list = []
        except Exception as e:
            Logger.info(f"unknown error: {e}")
            self.device_list = []

    def add_device(self, sn, ld):
        with self._lock:
            for device in self.device_list:
                if device.sn == sn:
                    Logger.info(f"Device with sn {sn} already exists, do not need repeated addition.")
                    return
            new_device = Device(sn, ld)
            self.device_list.append(new_device)
            Logger.info(f"Device {sn} has been added.")

    def remove_device(self, sn):
        with self._lock:
            for device in self.device_list:
                if device.sn == sn:
                    self.device_list.remove(device)
                    Logger.info(f"Device {sn} has been removed.")
                    return
            Logger.info(f"Device {sn} not exists.")

    def get_available_device(self):
        with self._lock:
            for device in self.device_list:
                if not device.isBusy:
                    device.isBusy = True
                    return device
            return None

    def release_device(self, sn):
        with self._lock:
            for device in self.device_list:
                if device.sn == sn:
                    device.isBusy = False
                    Logger.info(f"Device {sn} has been released.")
                    return
            Logger.info(f"Device {sn} not exists.")

    def update_device(self, old_sn, new_sn, new_ld):
        with self._lock:
            for device in self.device_list:
                if device.sn == old_sn:
                    device.sn = new_sn
                    device.ld = new_ld
                    Logger.info(f"Device {old_sn} has been updated to {new_sn}.")
                    return
            Logger.info(f"Device {old_sn} not exists, unable to update.")

    def get_device_list(self):
        return self.device_list


class FlashTool:
    def __init__(self, arch="arm", imageDir=""):
        if imageDir == "":
            self.outDir = '%s/out/ohos-%s-release' % (os.getcwd(), arch)
            self.imageDir = '%s/packages/phone/images' % self.outDir
        else:
            self.imageDir = imageDir
        self.toolDir = os.path.split(os.path.realpath(__file__))[0]
        self.flashTool = '%s/bin/flash.%s' % (self.toolDir, os.uname().machine)

    def QueryFlashMode(self):
        cmd = '%s LD' % self.flashTool
        fd = os.popen(cmd)
        line = fd.read()
        if line.find("Mode=Loader") >= 0:
            return DeviceMode.LOADER
        elif line.find("Mode=Maskrom") >= 0:
            return DeviceMode.MASKROM
        else:
            return DeviceMode.NONE

    def get_all_device_info(self):
        cmd = '%s LD' % self.flashTool
        fd = os.popen(cmd)
        output = fd.read()
        fd.close()
        lines = output.splitlines()
        for line in lines:
            if "LocationID" in line:
                parts = line.split()
                location_id = None
                mode = None
                serial_no = None
                for part in parts:
                    if part.startswith("LocationID="):
                        location_id = part.split("=")[1]
                    elif part.startswith("Mode="):
                        mode = part.split("=")[1]
                    elif part.startswith("SerialNo="):
                        serial_no = part.split("=")[1]

    def FlashReboot(self):
        return '%s rd ' % self.flashTool

    def FlashLoader(self):
        return '%s UL %s/MiniLoaderAll.bin' % (self.flashTool, self.imageDir)

    def FlashParameter(self):
        return '%s DI -p %s/parameter.txt' % (self.flashTool, self.imageDir)

    def FlashOne(self, part):
        if part in ('uboot', 'trust'):
            return '%s DI -%s %s/%s.img %s/parameter.txt' % (self.flashTool, part, self.imageDir, part, self.imageDir)
        else:
            return '%s DI -%s %s/%s.img' % (self.flashTool, part, self.imageDir, part)


class UpgradeTool:
    def __init__(self, image_path):
        if image_path == "":
            Logger.error("image path is empty")
        else:
            self.image_path = image_path
        self.tool_dir = os.path.split(os.path.realpath(__file__))[0]
        self.upgrade_tool = '%s/bin/upgrade_tool' % (self.tool_dir)

    def query_mode(self):
        cmd = '%s ld' % self.upgrade_tool
        Logger.info(f"{cmd}")
        fd = os.popen(cmd)
        line = fd.read()
        if line.find("Mode=Loader") >= 0:
            return DeviceMode.LOADER
        elif line.find("Mode=Maskrom") >= 0:
            return DeviceMode.MASKROM
        else:
            return DeviceMode.NONE

    def flash_loader(self):
        cmd = '%s db %s/MiniLoaderAll.bin' % (self.upgrade_tool, self.image_path)
        Logger.info(f"{cmd}")
        os.system(cmd)

    def flash_parameter(self):
        cmd = '%s di -p %s/parameter.txt' %(self.upgrade_tool, self.image_path)
        Logger.info(f"{cmd}")
        os.system(cmd)

    def flash_one(self, part):
        cmd = '%s di -%s %s/%s.img' % (self.upgrade_tool, part, self.image_path, part)
        Logger.info(f"{cmd}")
        os.system(cmd)

    def flash_reboot(self):
        cmd = '%s rd' % (self.upgrade_tool)
        Logger.info(f"{cmd}")
        os.system(cmd)
